package com.wgo.bpot.server.web.service;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.log4j.Logger;

import com.wgo.bpot.common.removeme.Persistent;
import com.wgo.bpot.common.transport.exception.db.DatabaseException;
import com.wgo.bpot.common.transport.servicefacade.DispatchEntry;
import com.wgo.bpot.common.transport.servicefacade.DispatchedModifyingMethodsTransport;
import com.wgo.bpot.common.transport.servicefacade.LightService;
import com.wgo.bpot.common.transport.util.Assert;
import com.wgo.bpot.common.transport.util.DomainModelTypeNamingConvention;
import com.wgo.bpot.server.persist.PersistService;
import com.wgo.bpot.server.persist.trigger.CrudOperation;
import com.wgo.bpot.server.persist.trigger.TriggerRegistry;
import com.wgo.bpot.server.web.util.PropertyAccessor;
import com.wgo.bpot.server.web.util.ServerModelConverter;
/**
 * The service implementation class.
 * 
 * @author petterei
 * @version $Id: LightServiceImpl.java,v 1.9 2006-02-06 09:22:24 petterei Exp $
 */
public class LightServiceImpl implements LightService{
	
	private static final transient Logger log = Logger.getLogger(LightServiceImpl.class);
	private TriggerRegistry triggerRegistry;
	private PersistService persistService;

	
	public void setPersistService(PersistService persistService) {
		this.persistService = persistService;
	}

	/**
	 * @see com.wgo.bpot.common.transport.servicefacade.LightService#createDbSchema()
	 */
	public void createDbSchema(Class rootConceptClass) throws DatabaseException {
		Assert.assertNotNull(rootConceptClass, "The API-System-class cannot be null!");
		persistService.setRootConceptClass(rootConceptClass);
		persistService.createSchema();
	}

	/**
	 * @see com.wgo.bpot.common.transport.servicefacade.LightService#retrieveClientVersions()
	 */
	public String[] retrieveClientVersions() {
		return PropertyAccessor.getString("server.compability.client.versions").split(" ");
	}

	/**
	 * @see com.wgo.bpot.common.transport.servicefacade.LightService#retrieveCompletePersistent(java.lang.Class, java.lang.Long, int)
	 */
	@SuppressWarnings("unchecked")
	public <T extends Object> T retrieveCompletePersistent(Class<T> facadeClass, Long identifier, int linkDepth) {
		Class ejbClass = ServerModelConverter.loadServerClass(facadeClass);
		Persistent ejbInstance = (Persistent) persistService.getInstance(ejbClass, identifier);
		if (null == ejbInstance) {
			log.warn("Could not load " + facadeClass + ", dbId: " + identifier + "from database.");
		} 
		return (T)ServerModelConverter.convertToDomainModel(ejbInstance, linkDepth , new Hashtable<String,Object>(),getTriggerRegistry());
	}

	
	/**
	 * @see com.wgo.bpot.common.transport.servicefacade.LightService#retrieveRequirementSystem(int)
	 */
	  @SuppressWarnings("unchecked")
	public Object retrieveRootSystem(int linkDepth, Class rootSystemApiClass){
		Assert.assertNotNull(rootSystemApiClass, "The API-System-class cannot be null!");
		persistService.setRootConceptClass(rootSystemApiClass);
		Set systems = new HashSet();
		try {
			Class reqSysClassEjb = DomainModelTypeNamingConvention.loadServerClass(rootSystemApiClass);
			Object reqSysEjb = reqSysClassEjb.newInstance();
			Method m = reqSysClassEjb.getMethod("getDbId");
			Long id = (Long)ServerModelConverter.invokeMethod(reqSysEjb, m);
			reqSysEjb = persistService.getInstance(reqSysClassEjb, id);
			if (null == reqSysEjb) {
				reqSysEjb = reqSysClassEjb.newInstance();
				persistService.save(reqSysEjb);
			}
			Map<String, Object> backTrace = new Hashtable<String,Object>();
			Object element = ServerModelConverter.convertToDomainModel((Persistent)reqSysEjb, linkDepth , backTrace,getTriggerRegistry());
			systems.add(element);
		} catch (Exception e) {
			throw new DatabaseException("Failed accessing or creating RootConcept!",e);
		}
		Object[] result = systems.toArray();
		log.info("retrieveSystems Returning Object[] containing " + result[0].getClass().getName());
		return result[0];
	}

	/**
	 * @see com.wgo.bpot.common.transport.servicefacade.LightService#save(DispatchedModifyingMethodsTransport[])
	 */
	@SuppressWarnings("unchecked")
	public Persistent[] save(DispatchedModifyingMethodsTransport[] dirtyInfo) {
		List<Persistent> updatedConcepts = new ArrayList<Persistent>();
		List<Persistent> createdConcepts = new ArrayList<Persistent>();
		int numConcepts = dirtyInfo.length;
		DatabaseLoadEntry[] dbEntries = new DatabaseLoadEntry[numConcepts];
		Map<Persistent, Persistent> domainToEjb = new Hashtable<Persistent, Persistent>();
		Map<Persistent, Set<DependentDispatch>> dirtyToDependentDispatches = new Hashtable<Persistent, Set<DependentDispatch>>();
		for (int i = 0; i < numConcepts ; i++) {
			Persistent dirtyConcept = dirtyInfo[i].getTargetConcept();
			Class<Persistent> ejbImplClass = ServerModelConverter.loadServerClass(dirtyConcept.getClass().getInterfaces()[0]);
			Persistent ejbInstance = null;
			if (null == dirtyConcept.getDbId()) {
				try {
					ejbInstance = ejbImplClass.newInstance();
					persistService.save(ejbInstance);
					createdConcepts.add(ejbInstance);
				} catch (Throwable e) {
					throw new DatabaseException("Could not instantiate ejb instance from domain model " + dirtyConcept.getClass().getInterfaces()[0], e);
				}
			} else {
				if (null == ejbInstance) {
					ejbInstance = domainToEjb.get(dirtyConcept);
				}				
				if (null == ejbInstance) {
					ejbInstance = load(ejbImplClass, dirtyConcept.getDbId(), persistService);
				}
				updatedConcepts.add(ejbInstance);
			}
			domainToEjb.put(dirtyConcept, ejbInstance);
			dbEntries[i] =  new DatabaseLoadEntry(ejbInstance.getDbId(), ejbImplClass);
			List<DispatchEntry> nonDependent = dirtyInfo[i].getNonDependentMethods();
			// invoke nondependent
			for (DispatchEntry entry : nonDependent) {
				ServerModelConverter.invokeMethod(ejbInstance, entry.getMethod(), entry.getArgument());
			}
			// invoke outstanding dependencies
			if (dirtyToDependentDispatches.containsKey(dirtyConcept)) {
				Set<DependentDispatch> dependents = dirtyToDependentDispatches.remove(dirtyConcept);
				for (DependentDispatch dependent : dependents) {
					ServerModelConverter.invokeMethod(dependent.target, dependent.method, ejbInstance);
				}
			}
			// invoke single dependent :
			List<DispatchEntry> singleDependent = dirtyInfo[i].getSingleDependentMethods();
			for (DispatchEntry entry : singleDependent) {
				if (null != entry.getArgument()) {
					processDependneMethodDispatching((Persistent)entry.getArgument(), ejbInstance, entry.getMethod(), domainToEjb, dirtyToDependentDispatches, persistService);
				} else {
					ServerModelConverter.invokeMethod(ejbInstance, entry.getMethod(), new Object[] {null});
				}
			}
			// invoke multiple dependent :
			List<DispatchEntry> multipleDependent = dirtyInfo[i].getMultipleDependentMethods();
			for (DispatchEntry entry : multipleDependent) {
				Method method = entry.getMethod();
				for (Persistent argument : (Collection<Persistent>)entry.getArgument()) {
					processDependneMethodDispatching(argument, ejbInstance, method, domainToEjb, dirtyToDependentDispatches, persistService);
				}
			}
		}
		// should never occure
		if (! dirtyToDependentDispatches.isEmpty()) { // should not occure, if using TestNG or other test-suites, watch out for class variables, they are modified by the suites..
			log.fatal("Unresolved dependencies. Size: " + dirtyToDependentDispatches.size());
			for (Entry<Persistent, Set<DependentDispatch>> entry: dirtyToDependentDispatches.entrySet()){
				String dependentDescription = "dependent: " + entry.getKey()+ ", dbId: " + entry.getKey().getDbId();
				log.fatal(dependentDescription);
				for (DependentDispatch dependent : entry.getValue()) {
					log.fatal(dependent.method.getName()+" should have been dispatched on " + dependent.target + "[dbId:"+dependent.target.getDbId()+"], with "+ dependentDescription +" as argument" );
				}
			}		
			throw new DatabaseException("Unresolved dependencies. Size: " + dirtyToDependentDispatches.size());
		}
		for (Persistent ejbInstance : createdConcepts) {
			getTriggerRegistry().invokeBefore(CrudOperation.CREATE,ejbInstance);
		}
		for (Persistent ejbInstance : updatedConcepts) {
			getTriggerRegistry().invokeBefore(CrudOperation.UPDATE,ejbInstance);
		}
		// commit
//		session.commit(); 
//		session.startTransaction();
		for (Persistent ejbInstance : createdConcepts) {
			getTriggerRegistry().invokeAfter(CrudOperation.CREATE,ejbInstance);
		}
		for (Persistent ejbInstance : updatedConcepts) {
			getTriggerRegistry().invokeAfter(CrudOperation.UPDATE,ejbInstance);
		}
//		log.info("Saved " + createdConcepts.size() + " and updated " + updatedConcepts.size() + " concepts.");
		//TODO: catch exceptions and rollback
		
		// load from database to ensure "WYSIWYG" - could be skipped to increase performance
//		HibernateUtil.beginTransaction();
		Persistent[] savedConceptsArray = new Persistent[numConcepts];
		for (int i = 0; i < numConcepts; i++) {
			Persistent ejbInstance = (Persistent)persistService.getInstance(dbEntries[i].clazz,dbEntries[i].dbId);
			savedConceptsArray[i] = (Persistent)ServerModelConverter.convertToDomainModel(ejbInstance, 1, new Hashtable<String,Object>(),null);
		}
		return savedConceptsArray;
	}
	private void processDependneMethodDispatching(Persistent argument, Persistent targetEjbInstance, Method method, Map<Persistent, Persistent> domainToEjb, Map<Persistent, Set<DependentDispatch>> dirtyToDependentDispatches, PersistService persistService ) {
		Persistent ejbArgument = domainToEjb.get(argument);
		if (null == ejbArgument) {
			if (null == argument.getDbId()) {
				Set<DependentDispatch> dependentDispatches = dirtyToDependentDispatches.get(argument); 
				if (null == dependentDispatches) {
					dependentDispatches = new HashSet<DependentDispatch>();
					dirtyToDependentDispatches.put(argument, dependentDispatches);
				}
				dependentDispatches.add(new DependentDispatch(method, targetEjbInstance));
				return;
			}
			ejbArgument = load(argument, argument.getDbId(), persistService);
		}
		ServerModelConverter.invokeMethod(targetEjbInstance, method, ejbArgument);
	}

	@SuppressWarnings("unchecked")
	private Persistent load (Persistent domain, Long dbId, PersistService persistService) {
		Class<Persistent> ejbImplClass = ServerModelConverter.loadServerClass(domain.getClass().getInterfaces()[0]);
		return load(ejbImplClass, dbId, persistService);
	}
	private Persistent load (Class clazz, Long dbId, PersistService persistService) {
		Persistent ejbInstance = (Persistent)persistService.getInstance(clazz, dbId);
		if (null == ejbInstance) {
			throw new DatabaseException("Could not load ejb instance from database " + clazz.getInterfaces()[0]);
		}
		return ejbInstance;
	}
	
	private synchronized TriggerRegistry getTriggerRegistry(){
		if (null == triggerRegistry) {
			triggerRegistry = new TriggerRegistry(persistService);
		}
		return triggerRegistry;
	}
	
	public PersistService getPersistService(){
		return persistService;
	}

	private class DatabaseLoadEntry {
		private Long dbId = null;
		private Class clazz = null;
		private DatabaseLoadEntry(Long dbId, Class clazz) {
			this.dbId = dbId;
			this.clazz = clazz;
		}
	}
	private class DependentDispatch {
		Method method = null;
		Persistent target = null;
		private DependentDispatch(Method method, Persistent target) {
			this.method = method;
			this.target = target;
		}
	}
	
}